/** 
 * Copyright (C) 2018 Jeebiz (http://jeebiz.net).
 * All Rights Reserved. 
 */
package net.jeebiz.cloud.extras.core.setup.security.jwt;

import java.security.GeneralSecurityException;
import java.time.Duration;
import java.util.Base64;
import java.util.Map;

import javax.crypto.SecretKey;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.boot.biz.userdetails.JwtPayloadRepository;
import org.springframework.security.boot.biz.userdetails.SecurityPrincipal;
import org.springframework.security.boot.jwt.exception.AuthenticationJwtExpiredException;
import org.springframework.security.boot.jwt.exception.AuthenticationJwtIncorrectException;
import org.springframework.security.boot.jwt.exception.AuthenticationJwtInvalidException;
import org.springframework.security.boot.jwt.exception.AuthenticationJwtIssuedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;

import com.github.vindell.jwt.JwtPayload;
import com.github.vindell.jwt.exception.ExpiredJwtException;
import com.github.vindell.jwt.exception.IncorrectJwtException;
import com.github.vindell.jwt.exception.InvalidJwtToken;
import com.github.vindell.jwt.exception.JwtException;
import com.github.vindell.jwt.token.SignedWithSecretKeyJWTRepository;
import com.github.vindell.jwt.utils.SecretKeyUtils;
import com.google.common.collect.Maps;

import net.jeebiz.cloud.api.utils.Constants;
import net.jeebiz.cloud.api.utils.StringUtils;

@Component
public class DefaultJwtPayloadRepository extends JwtPayloadRepository {

	// 过期时间（1天），单位毫秒
	private static final long EXPIRE_TIME = 24 * 60 * 60 * 1000;
				
	private static byte[] base64Secret = Base64.getDecoder().decode("6Tb9jCzN1jXppMrsLYfXbERiGiGp4nNtXuVdTGV9qN0=");
	private static SecretKey secretKey = null;
	
	static {
		try {
			secretKey = SecretKeyUtils.genSecretKey(base64Secret, "HmacSHA256");
		} catch (GeneralSecurityException e) {
			e.printStackTrace();
		}
		
	}
	
	@Autowired
    private SignedWithSecretKeyJWTRepository backendJwtRepository;
	@Autowired
    private StringRedisTemplate stringRedisTemplate;
	
	@Override
	public String issueJwt(AbstractAuthenticationToken token) {
		
		try {
			
			// 利用登陆用户信息构造jwt
			Map<String, Object> claims = Maps.newHashMap();
			SecurityPrincipal principal =  (SecurityPrincipal) token.getPrincipal();
			
			claims.put("roles", StringUtils.collectionToCommaDelimitedString(principal.getRoles()));
			claims.put("perms", StringUtils.collectionToCommaDelimitedString(principal.getPerms()));
			claims.put("alias", principal.getAlias());
			claims.put("userid", principal.getUserid());
			claims.put("userkey", principal.getUserkey());
			claims.put("usercode", principal.getUsercode());
			claims.put("initial", principal.isInitial());
			
			/*
			 * Authz-User:{userid} 构成了唯一的Session，故以唯一值的md5结果作为jwt的keyId
			 */
			String keyId = DigestUtils.md5DigestAsHex(String.format("%s:%s", Constants.AUTHZ_USER, principal.getUserid()).getBytes());
			
			String jwtString = getBackendJwtRepository().issueJwt(secretKey, keyId, principal.getUserid(), 
					"wwww.knowway.cn", principal.getUserid(), claims, "HS256", EXPIRE_TIME);
			
			//	设置jwt授权信息到Redis缓存，以便实现 剔除会话、主动清理会话等功能
			getStringRedisTemplate().opsForValue().set(String.format("jwt-%s", keyId), jwtString, Duration.ofHours(24));
			
			return jwtString;
			
		} catch (JwtException e) {
			throw new AuthenticationJwtIssuedException("JWT issue error");
		}
		
	}

	@Override
	public boolean verify(AbstractAuthenticationToken token, boolean checkExpiry) throws AuthenticationException {
		try {
			String jwtString = String.valueOf(token.getPrincipal());
			return getBackendJwtRepository().verify(secretKey, jwtString, checkExpiry);
		} catch (ExpiredJwtException e) {
			throw new AuthenticationJwtExpiredException("JWT has expired");
		} catch (InvalidJwtToken e) {
			throw new AuthenticationJwtInvalidException("JWT has invalid");
		} catch (IncorrectJwtException e) {
			throw new AuthenticationJwtIncorrectException("JWT has incorrect");
		}
	}

	@Override
	public JwtPayload getPayload(AbstractAuthenticationToken token, boolean checkExpiry) {
		try {
			String jwtString = String.valueOf(token.getPrincipal());
			// 解析并检查jwt
			JwtPayload payload = getBackendJwtRepository().getPlayload(secretKey, jwtString, false);
			/*
			 * Authz-User:{userid} 构成了唯一的Session，故以唯一值的md5结果作为jwt的keyId
			 */
			String keyId = DigestUtils.md5DigestAsHex(String.format("%s:%s", Constants.AUTHZ_USER, payload.getClientId()).getBytes());
			// 判断jwt
			if(!getStringRedisTemplate().hasKey(String.format("jwt-%s", keyId))) {
				throw new AuthenticationJwtExpiredException("JWT has expired");
			}
			return payload;
		} catch (ExpiredJwtException e) {
			throw new AuthenticationJwtExpiredException("JWT has expired");
		} catch (InvalidJwtToken e) {
			throw new AuthenticationJwtInvalidException("JWT has invalid");
		} catch (IncorrectJwtException e) {
			throw new AuthenticationJwtIncorrectException("JWT has incorrect");
		}
	}

	public SignedWithSecretKeyJWTRepository getBackendJwtRepository() {
		return backendJwtRepository;
	}

	public void setBackendJwtRepository(SignedWithSecretKeyJWTRepository backendJwtRepository) {
		this.backendJwtRepository = backendJwtRepository;
	}

	public StringRedisTemplate getStringRedisTemplate() {
		return stringRedisTemplate;
	}

	public void setStringRedisTemplate(StringRedisTemplate stringRedisTemplate) {
		this.stringRedisTemplate = stringRedisTemplate;
	}

}
